using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using NBear.Common;

namespace NBear.Data
{
    public class BatchCommander
    {
        private Database db;
        private int batchSize;
        private DbTransaction tran;
        List<DbCommand> batchCommands;

        public BatchCommander(Database db, int batchSize, IsolationLevel il) : this(db, batchSize)
        {
            Check.Require(db != null, "Arguments error.", new ArgumentNullException("db"));
            Check.Require(batchSize > 0, "Arguments error - batchSize should > 0.");

            tran = db.BeginTransaction(il);
        }

        public BatchCommander(Database db, int batchSize)
        {
            Check.Require(db != null, "Arguments error.", new ArgumentNullException("db"));
            Check.Require(batchSize > 0, "Arguments error - batchSize should > 0.");

            this.db = db;
            this.batchSize = batchSize;
            batchCommands = new List<DbCommand>(batchSize);
        }

        public void Process(DbCommand cmd)
        {
            if (cmd == null)
            {
                return;
            }

            cmd.Transaction = null;
            cmd.Connection = null;
            batchCommands.Add(cmd);

            if (batchCommands.Count >= batchSize)
            {
                ExecuteBatch();
            }
        }

        private void ExecuteBatch()
        {
            try
            {
                DbCommand cmd = MergeCommands();
                if (tran != null)
                {
                    cmd.Transaction = tran;
                    cmd.Connection = tran.Connection;
                }
                else
                {
                    cmd.Connection = db.GetConnection();
                }

                Database.WriteLog(cmd);

                cmd.ExecuteNonQuery();
            }
            catch
            {
                if (tran != null)
                {
                    tran.Rollback();
                }

                throw;
            }

            batchCommands.Clear();
        }

        private DbCommand MergeCommands()
        {
            DbCommand cmd = db.GetConnection().CreateCommand();
            cmd.CommandType = CommandType.Text;

            StringBuilder sb = new StringBuilder();
            foreach (DbCommand item in batchCommands)
            {
                if (item.CommandType == CommandType.StoredProcedure)
                {
                    sb.Append(string.Format("{0} ", item.CommandText));
                    StringBuilder paramSb = new StringBuilder();
                    foreach (DbParameter p in item.Parameters)
                    {
                        if (p.Direction == ParameterDirection.Input || p.Direction == ParameterDirection.InputOutput)
                        {
                            paramSb.Append(FormatParamVal(p.Value) + ",");
                        }
                    }
                    sb.Append(paramSb.ToString().TrimEnd(','));
                }
                else
                {
                    string sql = item.CommandText;
                    foreach (DbParameter p in item.Parameters)
                    {
                        if (p.Direction == ParameterDirection.Input || p.Direction == ParameterDirection.InputOutput)
                        {
                            System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex("(" + "@" + @"[\w\d_]+)");
                            sql = r.Replace(sql, FormatParamVal(p.Value), 1);
                        }
                    }
                    sb.Append(sql);
                }

                sb.Append("; ");
            }
            cmd.CommandText = sb.ToString();
            return cmd;
        }

        private string FormatParamVal(object val)
        {
            if (val == null || val == DBNull.Value)
            {
                return "null";
            }
            else if (val.GetType() == typeof(string))
            {
                return string.Format("N'{0}'", val.ToString().Replace("'", "''"));
            }
            else if (val.GetType() == typeof(DateTime) || val.GetType() == typeof(Guid))
            {
                return string.Format("'{0}'", val.ToString().Replace("'", "''"));
            }
            else if (val.GetType().IsValueType)
            {
                return val.ToString();
            }
            else
            {
                return string.Format("'{0}'", val.ToString().Replace("'", "''"));
            }
        }

        public void Close()
        {
            if (tran != null)
            {
                tran.Commit();
            }

            db.CloseConnection(tran);
        }
    }
}
